//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2004
//
// Tool for grouping actions.
//

# include "StdAfx.h"
# include "CombinedAction.h"
# include "UndoRedo.h"
# include "SaveState.h"
# include "Menu.h"

class CBookmarkAction : public CEditAction {
  public:
	CBookmarkAction (int _iDepth, bool _bWasModified) : m_iDepth (_iDepth), m_bWasModified (_bWasModified) {}

	virtual void GetName (string_t & _str) const {_str.append ("------");}
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do () {return true;}
	virtual void Undo () {}
	virtual void Redo () {}

	virtual int GetDepthLevel () const {return m_iDepth;}

	bool GetWasModified () const {return m_bWasModified;}

  private:
	const int m_iDepth;
	const bool m_bWasModified;
};

class CCombinedAction : public CEditAction {
  public:
	CCombinedAction (int _iDepth, const char * _strName, bool _bWasModified) :
		m_iDepth (_iDepth),
		m_strName (_strName),
		m_bWasModified (_bWasModified)
	{}
	~CCombinedAction ();

	virtual void GetName (string_t & _str) const {_str.append (m_strName);}
	virtual cu_t CanUndo () const {return cuYes;}
	virtual bool Do () {
		m_Selection = g_Selection;
		return true;
	}
	virtual void Undo ();
	virtual void Redo ();

	virtual int GetDepthLevel () const {return -m_iDepth;}

	void AddAction (CEditAction * _pAction) {m_actionsUndo.push_back (_pAction);}

  private:
	int      const m_iDepth;
	string_t const m_strName;

	actions_t m_actionsUndo;
	actions_t m_actionsRedo;

	// The data used to restore state after undo/redo.
	find_info_m_t m_Selection;
	bool m_bWasModified;
};

CCombinedAction::~CCombinedAction () {
	actions_t::iterator i;
	for (i = m_actionsUndo.begin (); i != m_actionsUndo.end (); ++ i)
		delete * i;

	for (i = m_actionsRedo.begin (); i != m_actionsRedo.end (); ++ i)
		delete * i;
}

void CCombinedAction::Undo () {
	while (! m_actionsUndo.empty ()) {
		CEditAction * const pAction = m_actionsUndo.back ();
		m_actionsUndo.pop_back ();

		pAction->Undo ();

		m_actionsRedo.push_back (pAction);
	}

	g_Selection = m_Selection;
	g_bModified = m_bWasModified;
}

void CCombinedAction::Redo () {
	m_Selection = g_Selection;
	m_bWasModified = g_bModified;

	while (! m_actionsRedo.empty ()) {
		CEditAction * const pAction = m_actionsRedo.back ();
		m_actionsRedo.pop_back ();

		pAction->Redo ();

		m_actionsUndo.push_back (pAction);
	}
}

///////////////////////////////////

static int g_iDepth;

bool IsInCombinedAction () {
	return g_iDepth > 0;
}

int BeginCombinedAction () {
	++ g_iDepth;
	PerformEditAction (new CBookmarkAction (g_iDepth, g_bModified));
	return g_iDepth;
}

void EndCombinedAction (int _iDepth, const char * _strName) {
	size_t cNestedActions = 0;
	bool bWasModified = true;

	// Find the bookmark.
	actions_t::iterator i = g_Undo.end ();
	do {
		-- i;

		const int iDepth = (* i)->GetDepthLevel ();
		if (iDepth == g_iDepth)
			break;

		if (iDepth != -1) // NOTE: ignore 'invisible' actions.
			++ cNestedActions;
	} while (i != g_Undo.begin ());

	if ((* i)->GetDepthLevel () != g_iDepth) {
		// The bookmark is not found.
		assert (false);
		return;
	}
	const actions_t::iterator iBookmark = i;
	const CBookmarkAction * const pBookmark = static_cast <CBookmarkAction *> (* iBookmark);
	bWasModified = pBookmark->GetWasModified ();

	// Remove bookmark.
	++ i;
	g_Undo.erase (iBookmark);

	if (cNestedActions >= 2) {
		CCombinedAction * const pCA = new CCombinedAction (g_iDepth, _strName, bWasModified);

		// Move actions from g_Undo (from bookmark till last one) to the combined action.
		while (i != g_Undo.end ()) {
			actions_t::iterator i_next = i;
			++ i_next;

			pCA->AddAction (* i);
			g_Undo.erase (i);

			i = i_next;
		}

		PerformEditAction (pCA);
	} else
		// NOTE: in this case, there is no wraping by CCombinedAction which would be responsible for selection.
		while (i != g_Undo.end ()) {
			* i = new CSaveStateWrapperAction (* i, g_Selection, bWasModified);
			++ i;
		}

	UpdateMenu ();
	
	-- g_iDepth;
}
